/*******************************************************************************
 * Copyright (c) 2009 Luaj.org. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 ******************************************************************************/
package com.org.luaj.compiler;

import com.org.luaj.*;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;


public class LexState {

    protected static final String RESERVED_LOCAL_VAR_FOR_CONTROL = "(for control)";
    protected static final String RESERVED_LOCAL_VAR_FOR_STATE = "(for state)";
    protected static final String RESERVED_LOCAL_VAR_FOR_GENERATOR = "(for generator)";
    protected static final String RESERVED_LOCAL_VAR_FOR_STEP = "(for step)";
    protected static final String RESERVED_LOCAL_VAR_FOR_LIMIT = "(for limit)";
    protected static final String RESERVED_LOCAL_VAR_FOR_INDEX = "(for index)";

    // keywords array
    protected static final String[] RESERVED_LOCAL_VAR_KEYWORDS = new String[]{
            RESERVED_LOCAL_VAR_FOR_CONTROL,
            RESERVED_LOCAL_VAR_FOR_GENERATOR,
            RESERVED_LOCAL_VAR_FOR_INDEX,
            RESERVED_LOCAL_VAR_FOR_LIMIT,
            RESERVED_LOCAL_VAR_FOR_STATE,
            RESERVED_LOCAL_VAR_FOR_STEP
    };
    private static final Hashtable RESERVED_LOCAL_VAR_KEYWORDS_TABLE = new Hashtable();

    static {
        for (int i = 0; i < RESERVED_LOCAL_VAR_KEYWORDS.length; i++)
            RESERVED_LOCAL_VAR_KEYWORDS_TABLE.put(RESERVED_LOCAL_VAR_KEYWORDS[i], Boolean.TRUE);
    }

    private static final int EOZ = (-1);
    private static final int MAXSRC = 80;
    private static final int MAX_INT = Integer.MAX_VALUE - 2;
    private static final int UCHAR_MAX = 255; // TODO, convert to unicode CHAR_MAX?
    private static final int LUAI_MAXCCALLS = 200;

    private static final String LUA_QS(String s) {
        return "'" + s + "'";
    }

    private static final String LUA_QL(Object o) {
        return LUA_QS(String.valueOf(o));
    }

    private static final int LUA_COMPAT_LSTR = 1; // 1 for compatibility, 2 for old behavior
    private static final boolean LUA_COMPAT_VARARG = true;

    public static boolean isReservedKeyword(String varName) {
        return RESERVED_LOCAL_VAR_KEYWORDS_TABLE.containsKey(varName);
    }

    /*
     ** Marks the end of a patch list. It is an invalid value both as an absolute
     ** address, and as a list link (would link an element to itself).
     */
    static final int NO_JUMP = (-1);

    /*
     ** grep "ORDER OPR" if you change these enums
     */
    static final int
            OPR_ADD = 0, OPR_SUB = 1, OPR_MUL = 2, OPR_DIV = 3, OPR_MOD = 4, OPR_POW = 5,
            OPR_CONCAT = 6,
            OPR_NE = 7, OPR_EQ = 8,
            OPR_LT = 9, OPR_LE = 10, OPR_GT = 11, OPR_GE = 12,
            OPR_AND = 13, OPR_OR = 14,
            OPR_NOBINOPR = 15;

    static final int
            OPR_MINUS = 0, OPR_NOT = 1, OPR_LEN = 2, OPR_NOUNOPR = 3;

    /* exp kind */
    static final int
            VVOID = 0,    /* no value */
            VNIL = 1,
            VTRUE = 2,
            VFALSE = 3,
            VK = 4,        /* info = index of constant in `k' */
            VKNUM = 5,    /* nval = numerical value */
            VLOCAL = 6,    /* info = local register */
            VUPVAL = 7,       /* info = index of upvalue in `upvalues' */
            VGLOBAL = 8,    /* info = index of table, aux = index of global name in `k' */
            VINDEXED = 9,    /* info = table register, aux = index register (or `k') */
            VJMP = 10,        /* info = instruction pc */
            VRELOCABLE = 11,    /* info = instruction pc */
            VNONRELOC = 12,    /* info = result register */
            VCALL = 13,    /* info = instruction pc */
            VVARARG = 14;    /* info = instruction pc */

    /* semantics information */
    private static class SemInfo {
        LuaValue r;
        LuaString ts;
    }

    ;

    private static class Token {
        int token;
        final SemInfo seminfo = new SemInfo();

        public void set(Token other) {
            this.token = other.token;
            this.seminfo.r = other.seminfo.r;
            this.seminfo.ts = other.seminfo.ts;
        }
    }

    ;

    int current;  /* current character (charint) */
    int linenumber;  /* input line counter */
    int lastline;  /* line of last token `consumed' */
    final Token t = new Token();  /* current token */
    final Token lookahead = new Token();  /* look ahead token */
    FuncState fs;  /* `FuncState' is private to the parser */
    LuaC L;
    InputStream z;  /* input stream */
    byte[] buff;  /* buffer for tokens */
    int nbuff; /* length of buffer */
    LuaString source;  /* current source name */
    byte decpoint;  /* locale decimal point */

    /* ORDER RESERVED */
    final static String luaX_tokens[] = {
            "and", "break", "do", "else", "elseif",
            "end", "false", "for", "function", "if",
            "in", "local", "nil", "not", "or", "repeat",
            "return", "then", "true", "until", "while",
            "..", "...", "==", ">=", "<=", "~=",
            "<number>", "<name>", "<string>", "<eof>",
    };

    final static int
            /* terminal symbols denoted by reserved words */
            TK_AND = 257, TK_BREAK = 258, TK_DO = 259, TK_ELSE = 260, TK_ELSEIF = 261,
            TK_END = 262, TK_FALSE = 263, TK_FOR = 264, TK_FUNCTION = 265, TK_IF = 266,
            TK_IN = 267, TK_LOCAL = 268, TK_NIL = 269, TK_NOT = 270, TK_OR = 271, TK_REPEAT = 272,
            TK_RETURN = 273, TK_THEN = 274, TK_TRUE = 275, TK_UNTIL = 276, TK_WHILE = 277,
    /* other terminal symbols */
    TK_CONCAT = 278, TK_DOTS = 279, TK_EQ = 280, TK_GE = 281, TK_LE = 282, TK_NE = 283,
            TK_NUMBER = 284, TK_NAME = 285, TK_STRING = 286, TK_EOS = 287;

    final static int FIRST_RESERVED = TK_AND;
    final static int NUM_RESERVED = TK_WHILE + 1 - FIRST_RESERVED;

    final static Hashtable RESERVED = new Hashtable();

    static {
        for (int i = 0; i < NUM_RESERVED; i++) {
            LuaString ts = (LuaString) LuaValue.valueOf(luaX_tokens[i]);
            RESERVED.put(ts, new Integer(FIRST_RESERVED + i));
        }
    }

    private boolean isalnum(int c) {
        return (c >= '0' && c <= '9')
                || (c >= 'a' && c <= 'z')
                || (c >= 'A' && c <= 'Z')
                || (c == '_');
        // return Character.isLetterOrDigit(c);
    }

    private boolean isalpha(int c) {
        return (c >= 'a' && c <= 'z')
                || (c >= 'A' && c <= 'Z');
    }

    private boolean isdigit(int c) {
        return (c >= '0' && c <= '9');
    }

    private boolean isspace(int c) {
        return (c <= ' ');
    }


    public LexState(LuaC state, InputStream stream) {
        this.z = stream;
        this.buff = new byte[32];
        this.L = state;
    }

    void nextChar() {
        try {
            current = z.read();
        } catch (IOException e) {
            e.printStackTrace();
            current = EOZ;
        }
    }

    boolean currIsNewline() {
        return current == '\n' || current == '\r';
    }

    void save_and_next() {
        save(current);
        nextChar();
    }

    void save(int c) {
        if (buff == null || nbuff + 1 > buff.length)
            buff = LuaC.realloc(buff, nbuff * 2 + 1);
        buff[nbuff++] = (byte) c;
    }


    String token2str(int token) {
        if (token < FIRST_RESERVED) {
            return iscntrl(token) ?
                    L.pushfstring("char(" + ((int) token) + ")") :
                    L.pushfstring(String.valueOf((char) token));
        } else {
            return luaX_tokens[token - FIRST_RESERVED];
        }
    }

    private static boolean iscntrl(int token) {
        return token < ' ';
    }

    String txtToken(int token) {
        switch (token) {
            case TK_NAME:
            case TK_STRING:
            case TK_NUMBER:
                return new String(buff, 0, nbuff);
            default:
                return token2str(token);
        }
    }

    void lexerror(String msg, int token) {
        String cid = chunkid(source.tojstring()); // TODO: get source name from source
        L.pushfstring(cid + ":" + linenumber + ": " + msg);
        if (token != 0)
            L.pushfstring("syntax error: " + msg + " near " + txtToken(token));
        throw new LuaError(cid + ":" + linenumber + ": " + msg);
    }

    String chunkid(String source) {
        if (source.startsWith("="))
            return source.substring(1);
        String end = "";
        if (source.startsWith("@")) {
            source = source.substring(1);
        } else {
            source = "[string \"" + source;
            end = "\"]";
        }
        int n = source.length() + end.length();
        if (n > MAXSRC)
            source = source.substring(0, MAXSRC - end.length() - 3) + "...";
        return source + end;
    }

    void syntaxerror(String msg) {
        lexerror(msg, t.token);
    }

    // only called by new_localvarliteral() for var names.
    LuaString newstring(String s) {
        byte[] b = s.getBytes();
        return L.newTString(b, 0, b.length);
    }

    LuaString newstring(byte[] bytes, int offset, int len) {
        return L.newTString(bytes, offset, len);
    }

    void inclinenumber() {
        int old = current;
        LuaC._assert(currIsNewline());
        nextChar(); /* skip '\n' or '\r' */
        if (currIsNewline() && current != old)
            nextChar(); /* skip '\n\r' or '\r\n' */
        if (++linenumber >= MAX_INT)
            syntaxerror("chunk has too many lines");
    }

    void setinput(LuaC L, int firstByte, InputStream z, LuaString source) {
        this.decpoint = '.';
        this.L = L;
        this.lookahead.token = TK_EOS; /* no look-ahead token */
        this.z = z;
        this.fs = null;
        this.linenumber = 1;
        this.lastline = 1;
        this.source = source;
        this.nbuff = 0;   /* initialize buffer */
        this.current = firstByte; /* read first char */
        this.skipShebang();
    }

    private void skipShebang() {
        if (current == '#')
            while (!currIsNewline() && current != EOZ)
                nextChar();
    }



    /*
     ** =======================================================
     ** LEXICAL ANALYZER
     ** =======================================================
     */


    boolean check_next(String set) {
        if (set.indexOf(current) < 0)
            return false;
        save_and_next();
        return true;
    }

    void buffreplace(byte from, byte to) {
        int n = nbuff;
        byte[] p = buff;
        while ((--n) >= 0)
            if (p[n] == from)
                p[n] = to;
    }

    boolean str2d(String str, SemInfo seminfo) {
        double d;
        str = str.trim(); // TODO: get rid of this
        if (str.startsWith("0x")) {
            d = Long.parseLong(str.substring(2), 16);
        } else
            d = Double.parseDouble(str);
        seminfo.r = LuaValue.valueOf(d);
        return true;
    }

    //
    // TODO: reexamine this source and see if it should be ported differently
    //
    // static void trydecpoint (LexState *ls, SemInfo *seminfo) {
    //	  /* format error: try to update decimal point separator */
    //	  struct lconv *cv = localeconv();
    //	  char old = this.decpoint;
    //	  this.decpoint = (cv ? cv->decimal_point[0] : '.');
    //	  buffreplace(ls, old, this.decpoint);  /* try updated decimal separator */
    //	  if (!luaO_str2d(luaZ_buffer(this.buff), &seminfo->r)) {
    //	    /* format error with correct decimal point: no more options */
    //	    buffreplace(ls, this.decpoint, '.');  /* undo change (for error message) */
    //	    luaX_lexerror(ls, "malformed number", TK_NUMBER);
    //	  }
    //	}
    //
	/*
	void trydecpoint(String str, SemInfo seminfo) {
		NumberFormat nf = NumberFormat.getInstance();
		try {
			Number n = nf.parse(str);
			double d = n.doubleValue();
			seminfo.r = new LDouble(d);
		} catch (ParseException e) {
			lexerror("malformed number", TK_NUMBER);
		}
	}
	*/

    void read_numeral(SemInfo seminfo) {
        LuaC._assert(isdigit(current));
        do {
            save_and_next();
        } while (isdigit(current) || current == '.');
        if (check_next("Ee")) /* `E'? */
            check_next("+-"); /* optional exponent sign */
        while (isalnum(current) || current == '_')
            save_and_next();
        save('\0');
        buffreplace((byte) '.', decpoint); /* follow locale for decimal point */
        String str = new String(buff, 0, nbuff);
//		if (!str2d(str, seminfo)) /* format error? */
//			trydecpoint(str, seminfo); /* try to update decimal point separator */
        str2d(str, seminfo);
    }

    int skip_sep() {
        int count = 0;
        int s = current;
        LuaC._assert(s == '[' || s == ']');
        save_and_next();
        while (current == '=') {
            save_and_next();
            count++;
        }
        return (current == s) ? count : (-count) - 1;
    }

    void read_long_string(SemInfo seminfo, int sep) {
        int cont = 0;
        save_and_next(); /* skip 2nd `[' */
        if (currIsNewline()) /* string starts with a newline? */
            inclinenumber(); /* skip it */
        for (boolean endloop = false; !endloop; ) {
            switch (current) {
                case EOZ:
                    lexerror((seminfo != null) ? "unfinished long string"
                            : "unfinished long comment", TK_EOS);
                    break; /* to avoid warnings */
                case '[': {
                    if (skip_sep() == sep) {
                        save_and_next(); /* skip 2nd `[' */
                        cont++;
                        if (LUA_COMPAT_LSTR == 1) {
                            if (sep == 0)
                                lexerror("nesting of [[...]] is deprecated", '[');
                        }
                    }
                    break;
                }
                case ']': {
                    if (skip_sep() == sep) {
                        save_and_next(); /* skip 2nd `]' */
                        if (LUA_COMPAT_LSTR == 2) {
                            cont--;
                            if (sep == 0 && cont >= 0)
                                break;
                        }
                        endloop = true;
                    }
                    break;
                }
                case '\n':
                case '\r': {
                    save('\n');
                    inclinenumber();
                    if (seminfo == null)
                        nbuff = 0; /* avoid wasting space */
                    break;
                }
                default: {
                    if (seminfo != null)
                        save_and_next();
                    else
                        nextChar();
                }
            }
        }
        if (seminfo != null)
            seminfo.ts = newstring(buff, 2 + sep, nbuff - 2 * (2 + sep));
    }

    void read_string(int del, SemInfo seminfo) {
        save_and_next();
        while (current != del) {
            switch (current) {
                case EOZ:
                    lexerror("unfinished string", TK_EOS);
                    continue; /* to avoid warnings */
                case '\n':
                case '\r':
                    lexerror("unfinished string", TK_STRING);
                    continue; /* to avoid warnings */
                case '\\': {
                    int c;
                    nextChar(); /* do not save the `\' */
                    switch (current) {
                        case 'a': /* bell */
                            c = '\u0007';
                            break;
                        case 'b': /* backspace */
                            c = '\b';
                            break;
                        case 'f': /* form feed */
                            c = '\f';
                            break;
                        case 'n': /* newline */
                            c = '\n';
                            break;
                        case 'r': /* carriage return */
                            c = '\r';
                            break;
                        case 't': /* tab */
                            c = '\t';
                            break;
                        case 'v': /* vertical tab */
                            c = '\u000B';
                            break;
                        case '\n': /* go through */
                        case '\r':
                            save('\n');
                            inclinenumber();
                            continue;
                        case EOZ:
                            continue; /* will raise an error next loop */
                        default: {
                            if (!isdigit(current))
                                save_and_next(); /* handles \\, \", \', and \? */
                            else { /* \xxx */
                                int i = 0;
                                c = 0;
                                do {
                                    c = 10 * c + (current - '0');
                                    nextChar();
                                } while (++i < 3 && isdigit(current));
                                if (c > UCHAR_MAX)
                                    lexerror("escape sequence too large", TK_STRING);
                                save(c);
                            }
                            continue;
                        }
                    }
                    save(c);
                    nextChar();
                    continue;
                }
                default:
                    save_and_next();
            }
        }
        save_and_next(); /* skip delimiter */
        seminfo.ts = newstring(buff, 1, nbuff - 2);
    }

    int llex(SemInfo seminfo) {
        nbuff = 0;
        while (true) {
            switch (current) {
                case '\n':
                case '\r': {
                    inclinenumber();
                    continue;
                }
                case '-': {
                    nextChar();
                    if (current != '-')
                        return '-';
                    /* else is a comment */
                    nextChar();
                    if (current == '[') {
                        int sep = skip_sep();
                        nbuff = 0; /* `skip_sep' may dirty the buffer */
                        if (sep >= 0) {
                            read_long_string(null, sep); /* long comment */
                            nbuff = 0;
                            continue;
                        }
                    }
                    /* else short comment */
                    while (!currIsNewline() && current != EOZ)
                        nextChar();
                    continue;
                }
                case '[': {
                    int sep = skip_sep();
                    if (sep >= 0) {
                        read_long_string(seminfo, sep);
                        return TK_STRING;
                    } else if (sep == -1)
                        return '[';
                    else
                        lexerror("invalid long string delimiter", TK_STRING);
                }
                case '=': {
                    nextChar();
                    if (current != '=')
                        return '=';
                    else {
                        nextChar();
                        return TK_EQ;
                    }
                }
                case '<': {
                    nextChar();
                    if (current != '=')
                        return '<';
                    else {
                        nextChar();
                        return TK_LE;
                    }
                }
                case '>': {
                    nextChar();
                    if (current != '=')
                        return '>';
                    else {
                        nextChar();
                        return TK_GE;
                    }
                }
                case '~': {
                    nextChar();
                    if (current != '=')
                        return '~';
                    else {
                        nextChar();
                        return TK_NE;
                    }
                }
                case '"':
                case '\'': {
                    read_string(current, seminfo);
                    return TK_STRING;
                }
                case '.': {
                    save_and_next();
                    if (check_next(".")) {
                        if (check_next("."))
                            return TK_DOTS; /* ... */
                        else
                            return TK_CONCAT; /* .. */
                    } else if (!isdigit(current))
                        return '.';
                    else {
                        read_numeral(seminfo);
                        return TK_NUMBER;
                    }
                }
                case EOZ: {
                    return TK_EOS;
                }
                default: {
                    if (isspace(current)) {
                        LuaC._assert(!currIsNewline());
                        nextChar();
                        continue;
                    } else if (isdigit(current)) {
                        read_numeral(seminfo);
                        return TK_NUMBER;
                    } else if (isalpha(current) || current == '_') {
                        /* identifier or reserved word */
                        LuaString ts;
                        do {
                            save_and_next();
                        } while (isalnum(current) || current == '_');
                        ts = newstring(buff, 0, nbuff);
                        if (RESERVED.containsKey(ts))
                            return ((Integer) RESERVED.get(ts)).intValue();
                        else {
                            seminfo.ts = ts;
                            return TK_NAME;
                        }
                    } else {
                        int c = current;
                        nextChar();
                        return c; /* single-char tokens (+ - / ...) */
                    }
                }
            }
        }
    }

    void next() {
        lastline = linenumber;
        if (lookahead.token != TK_EOS) { /* is there a look-ahead token? */
            t.set(lookahead); /* use this one */
            lookahead.token = TK_EOS; /* and discharge it */
        } else
            t.token = llex(t.seminfo); /* read next token */
    }

    void lookahead() {
        LuaC._assert(lookahead.token == TK_EOS);
        lookahead.token = llex(lookahead.seminfo);
    }

    // =============================================================
    // from lcode.h
    // =============================================================


    // =============================================================
    // from lparser.c
    // =============================================================

    static class expdesc {
        int k; // expkind, from enumerated list, above

        static class U { // originally a union
            static class S {
                int info, aux;
            }

            final S s = new S();
            private LuaValue _nval;

            public void setNval(LuaValue r) {
                _nval = r;
            }

            public LuaValue nval() {
                return (_nval == null ? LuaLong.valueOf(s.info) : _nval);
            }
        }

        ;
        final U u = new U();
        final IntPtr t = new IntPtr(); /* patch list of `exit when true' */
        final IntPtr f = new IntPtr(); /* patch list of `exit when false' */

        void init(int k, int i) {
            this.f.i = NO_JUMP;
            this.t.i = NO_JUMP;
            this.k = k;
            this.u.s.info = i;
        }

        boolean hasjumps() {
            return (t.i != f.i);
        }

        boolean isnumeral() {
            return (k == VKNUM && t.i == NO_JUMP && f.i == NO_JUMP);
        }

        public void setvalue(expdesc other) {
            this.k = other.k;
            this.u._nval = other.u._nval;
            this.u.s.info = other.u.s.info;
            this.u.s.aux = other.u.s.aux;
            this.t.i = other.t.i;
            this.f.i = other.f.i;
        }
    }

    boolean hasmultret(int k) {
        return ((k) == VCALL || (k) == VVARARG);
    }

	/*----------------------------------------------------------------------
	name		args	description
	------------------------------------------------------------------------*/

    /*
     * * prototypes for recursive non-terminal functions
     */

    void error_expected(int token) {
        syntaxerror(L.pushfstring(LUA_QS(token2str(token)) + " expected"));
    }

    boolean testnext(int c) {
        if (t.token == c) {
            next();
            return true;
        } else
            return false;
    }

    void check(int c) {
        if (t.token != c)
            error_expected(c);
    }

    void checknext(int c) {
        check(c);
        next();
    }

    void check_condition(boolean c, String msg) {
        if (!(c))
            syntaxerror(msg);
    }


    void check_match(int what, int who, int where) {
        if (!testnext(what)) {
            if (where == linenumber)
                error_expected(what);
            else {
                syntaxerror(L.pushfstring(LUA_QS(token2str(what))
                        + " expected " + "(to close " + LUA_QS(token2str(who))
                        + " at line " + where + ")"));
            }
        }
    }

    LuaString str_checkname() {
        LuaString ts;
        check(TK_NAME);
        ts = t.seminfo.ts;
        next();
        return ts;
    }

    void codestring(expdesc e, LuaString s) {
        e.init(VK, fs.stringK(s));
    }

    void checkname(expdesc e) {
        codestring(e, str_checkname());
    }


    int registerlocalvar(LuaString varname) {
        FuncState fs = this.fs;
        Prototype f = fs.f;
        if (f.locvars == null || fs.nlocvars + 1 > f.locvars.length)
            f.locvars = LuaC.realloc(f.locvars, fs.nlocvars * 2 + 1);
        f.locvars[fs.nlocvars] = new LocVars(varname, 0, 0);
        return fs.nlocvars++;
    }


    //
//	#define new_localvarliteral(ls,v,n) \
//	  this.new_localvar(luaX_newstring(ls, "" v, (sizeof(v)/sizeof(char))-1), n)
//
    void new_localvarliteral(String v, int n) {
        LuaString ts = newstring(v);
        new_localvar(ts, n);
    }

    void new_localvar(LuaString name, int n) {
        FuncState fs = this.fs;
        fs.checklimit(fs.nactvar + n + 1, FuncState.LUAI_MAXVARS, "local variables");
        fs.actvar[fs.nactvar + n] = (short) registerlocalvar(name);
    }

    void adjustlocalvars(int nvars) {
        FuncState fs = this.fs;
        fs.nactvar = (short) (fs.nactvar + nvars);
        for (; nvars > 0; nvars--) {
            fs.getlocvar(fs.nactvar - nvars).startpc = fs.pc;
        }
    }

    void removevars(int tolevel) {
        FuncState fs = this.fs;
        while (fs.nactvar > tolevel)
            fs.getlocvar(--fs.nactvar).endpc = fs.pc;
    }

    void singlevar(expdesc var) {
        LuaString varname = this.str_checkname();
        FuncState fs = this.fs;
        if (fs.singlevaraux(varname, var, 1) == VGLOBAL)
            var.u.s.info = fs.stringK(varname); /* info points to global name */
    }

    void adjust_assign(int nvars, int nexps, expdesc e) {
        FuncState fs = this.fs;
        int extra = nvars - nexps;
        if (hasmultret(e.k)) {
            /* includes call itself */
            extra++;
            if (extra < 0)
                extra = 0;
            /* last exp. provides the difference */
            fs.setreturns(e, extra);
            if (extra > 1)
                fs.reserveregs(extra - 1);
        } else {
            /* close last expression */
            if (e.k != VVOID)
                fs.exp2nextreg(e);
            if (extra > 0) {
                int reg = fs.freereg;
                fs.reserveregs(extra);
                fs.nil(reg, extra);
            }
        }
    }

    void enterlevel() {
        if (++L.nCcalls > LUAI_MAXCCALLS)
            lexerror("chunk has too many syntax levels", 0);
    }

    void leavelevel() {
        L.nCcalls--;
    }

    void pushclosure(FuncState func, expdesc v) {
        FuncState fs = this.fs;
        Prototype f = fs.f;
        if (f.p == null || fs.np + 1 > f.p.length)
            f.p = LuaC.realloc(f.p, fs.np * 2 + 1);
        f.p[fs.np++] = func.f;
        v.init(VRELOCABLE, fs.codeABx(Lua.OP_CLOSURE, 0, fs.np - 1));
        for (int i = 0; i < func.f.nups; i++) {
            int o = (func.upvalues[i].k == VLOCAL) ? Lua.OP_MOVE
                    : Lua.OP_GETUPVAL;
            fs.codeABC(o, 0, func.upvalues[i].info, 0);
        }
    }

    void open_func(FuncState fs) {
        LuaC L = this.L;
        Prototype f = new Prototype();
        if (this.fs != null)
            f.source = this.fs.f.source;
        fs.f = f;
        fs.prev = this.fs;  /* linked list of funcstates */
        fs.ls = this;
        fs.L = L;
        this.fs = fs;
        fs.pc = 0;
        fs.lasttarget = -1;
        fs.jpc = new IntPtr(NO_JUMP);
        fs.freereg = 0;
        fs.nk = 0;
        fs.np = 0;
        fs.nlocvars = 0;
        fs.nactvar = 0;
        fs.bl = null;
        f.maxstacksize = 2;  /* registers 0/1 are always valid */
        //fs.h = new LTable();
        fs.htable = new Hashtable();
    }

    void close_func() {
        FuncState fs = this.fs;
        Prototype f = fs.f;
        this.removevars(0);
        fs.ret(0, 0); /* final return */
        f.code = LuaC.realloc(f.code, fs.pc);
        f.lineinfo = LuaC.realloc(f.lineinfo, fs.pc);
        // f.sizelineinfo = fs.pc;
        f.k = LuaC.realloc(f.k, fs.nk);
        f.p = LuaC.realloc(f.p, fs.np);
        f.locvars = LuaC.realloc(f.locvars, fs.nlocvars);
        // f.sizelocvars = fs.nlocvars;
        f.upvalues = LuaC.realloc(f.upvalues, f.nups);
        // LuaC._assert (CheckCode.checkcode(f));
        LuaC._assert(fs.bl == null);
        this.fs = fs.prev;
//		L.top -= 2; /* remove table and prototype from the stack */
        // /* last token read was anchored in defunct function; must reanchor it
        // */
        // if (fs!=null) ls.anchor_token();
    }

    /*============================================================*/
    /* GRAMMAR RULES */
    /*============================================================*/

    void field(expdesc v) {
        /* field -> ['.' | ':'] NAME */
        FuncState fs = this.fs;
        expdesc key = new expdesc();
        fs.exp2anyreg(v);
        this.next(); /* skip the dot or colon */
        this.checkname(key);
        fs.indexed(v, key);
    }

    void yindex(expdesc v) {
        /* index -> '[' expr ']' */
        this.next(); /* skip the '[' */
        this.expr(v);
        this.fs.exp2val(v);
        this.checknext(']');
    }


    /*
     ** {======================================================================
     ** Rules for Constructors
     ** =======================================================================
     */


    static class ConsControl {
        expdesc v = new expdesc(); /* last list item read */
        expdesc t; /* table descriptor */
        int nh; /* total number of `record' elements */
        int na; /* total number of array elements */
        int tostore; /* number of array elements pending to be stored */
    }

    ;


    void recfield(ConsControl cc) {
        /* recfield -> (NAME | `['exp1`]') = exp1 */
        FuncState fs = this.fs;
        int reg = this.fs.freereg;
        expdesc key = new expdesc();
        expdesc val = new expdesc();
        int rkkey;
        if (this.t.token == TK_NAME) {
            fs.checklimit(cc.nh, MAX_INT, "items in a constructor");
            this.checkname(key);
        } else
            /* this.t.token == '[' */
            this.yindex(key);
        cc.nh++;
        this.checknext('=');
        rkkey = fs.exp2RK(key);
        this.expr(val);
        fs.codeABC(Lua.OP_SETTABLE, cc.t.u.s.info, rkkey, fs.exp2RK(val));
        fs.freereg = reg; /* free registers */
    }

    void listfield(ConsControl cc) {
        this.expr(cc.v);
        fs.checklimit(cc.na, MAX_INT, "items in a constructor");
        cc.na++;
        cc.tostore++;
    }


    void constructor(expdesc t) {
        /* constructor -> ?? */
        FuncState fs = this.fs;
        int line = this.linenumber;
        int pc = fs.codeABC(Lua.OP_NEWTABLE, 0, 0, 0);
        ConsControl cc = new ConsControl();
        cc.na = cc.nh = cc.tostore = 0;
        cc.t = t;
        t.init(VRELOCABLE, pc);
        cc.v.init(VVOID, 0); /* no value (yet) */
        fs.exp2nextreg(t); /* fix it at stack top (for gc) */
        this.checknext('{');
        do {
            LuaC._assert(cc.v.k == VVOID || cc.tostore > 0);
            if (this.t.token == '}')
                break;
            fs.closelistfield(cc);
            switch (this.t.token) {
                case TK_NAME: { /* may be listfields or recfields */
                    this.lookahead();
                    if (this.lookahead.token != '=') /* expression? */
                        this.listfield(cc);
                    else
                        this.recfield(cc);
                    break;
                }
                case '[': { /* constructor_item -> recfield */
                    this.recfield(cc);
                    break;
                }
                default: { /* constructor_part -> listfield */
                    this.listfield(cc);
                    break;
                }
            }
        } while (this.testnext(',') || this.testnext(';'));
        this.check_match('}', '{', line);
        fs.lastlistfield(cc);
        InstructionPtr i = new InstructionPtr(fs.f.code, pc);
        LuaC.SETARG_B(i, luaO_int2fb(cc.na)); /* set initial array size */
        LuaC.SETARG_C(i, luaO_int2fb(cc.nh));  /* set initial table size */
    }

    /*
     ** converts an integer to a "floating point byte", represented as
     ** (eeeeexxx), where the real value is (1xxx) * 2^(eeeee - 1) if
     ** eeeee != 0 and (xxx) otherwise.
     */
    static int luaO_int2fb(int x) {
        int e = 0;  /* expoent */
        while (x >= 16) {
            x = (x + 1) >> 1;
            e++;
        }
        if (x < 8) return x;
        else return ((e + 1) << 3) | (((int) x) - 8);
    }


    /* }====================================================================== */

    void parlist() {
        /* parlist -> [ param { `,' param } ] */
        FuncState fs = this.fs;
        Prototype f = fs.f;
        int nparams = 0;
        f.is_vararg = 0;
        if (this.t.token != ')') {  /* is `parlist' not empty? */
            do {
                switch (this.t.token) {
                    case TK_NAME: {  /* param . NAME */
                        this.new_localvar(this.str_checkname(), nparams++);
                        break;
                    }
                    case TK_DOTS: {  /* param . `...' */
                        this.next();
                        if (LUA_COMPAT_VARARG) {
                            /* use `arg' as default name */
                            this.new_localvarliteral("arg", nparams++);
                            f.is_vararg = LuaC.VARARG_HASARG | LuaC.VARARG_NEEDSARG;
                        }
                        f.is_vararg |= LuaC.VARARG_ISVARARG;
                        break;
                    }
                    default:
                        this.syntaxerror("<name> or " + LUA_QL("...") + " expected");
                }
            } while ((f.is_vararg == 0) && this.testnext(','));
        }
        this.adjustlocalvars(nparams);
        f.numparams = (fs.nactvar - (f.is_vararg & LuaC.VARARG_HASARG));
        fs.reserveregs(fs.nactvar);  /* reserve register for parameters */
    }


    void body(expdesc e, boolean needself, int line) {
        /* body -> `(' parlist `)' chunk END */
        FuncState new_fs = new FuncState();
        open_func(new_fs);
        new_fs.f.linedefined = line;
        this.checknext('(');
        if (needself) {
            new_localvarliteral("self", 0);
            adjustlocalvars(1);
        }
        this.parlist();
        this.checknext(')');
        this.chunk();
        new_fs.f.lastlinedefined = this.linenumber;
        this.check_match(TK_END, TK_FUNCTION, line);
        this.close_func();
        this.pushclosure(new_fs, e);
    }

    int explist1(expdesc v) {
        /* explist1 -> expr { `,' expr } */
        int n = 1; /* at least one expression */
        this.expr(v);
        while (this.testnext(',')) {
            fs.exp2nextreg(v);
            this.expr(v);
            n++;
        }
        return n;
    }


    void funcargs(expdesc f) {
        FuncState fs = this.fs;
        expdesc args = new expdesc();
        int base, nparams;
        int line = this.linenumber;
        switch (this.t.token) {
            case '(': { /* funcargs -> `(' [ explist1 ] `)' */
                if (line != this.lastline)
                    this.syntaxerror("ambiguous syntax (function call x new statement)");
                this.next();
                if (this.t.token == ')') /* arg list is empty? */
                    args.k = VVOID;
                else {
                    this.explist1(args);
                    fs.setmultret(args);
                }
                this.check_match(')', '(', line);
                break;
            }
            case '{': { /* funcargs -> constructor */
                this.constructor(args);
                break;
            }
            case TK_STRING: { /* funcargs -> STRING */
                this.codestring(args, this.t.seminfo.ts);
                this.next(); /* must use `seminfo' before `next' */
                break;
            }
            default: {
                this.syntaxerror("function arguments expected");
                return;
            }
        }
        LuaC._assert(f.k == VNONRELOC);
        base = f.u.s.info; /* base register for call */
        if (hasmultret(args.k))
            nparams = Lua.LUA_MULTRET; /* open call */
        else {
            if (args.k != VVOID)
                fs.exp2nextreg(args); /* close last argument */
            nparams = fs.freereg - (base + 1);
        }
        f.init(VCALL, fs.codeABC(Lua.OP_CALL, base, nparams + 1, 2));
        fs.fixline(line);
        fs.freereg = base + 1;  /* call remove function and arguments and leaves
         * (unless changed) one result */
    }


    /*
     ** {======================================================================
     ** Expression parsing
     ** =======================================================================
     */

    void prefixexp(expdesc v) {
        /* prefixexp -> NAME | '(' expr ')' */
        switch (this.t.token) {
            case '(': {
                int line = this.linenumber;
                this.next();
                this.expr(v);
                this.check_match(')', '(', line);
                fs.dischargevars(v);
                return;
            }
            case TK_NAME: {
                this.singlevar(v);
                return;
            }
            default: {
                this.syntaxerror("unexpected symbol");
                return;
            }
        }
    }


    void primaryexp(expdesc v) {
        /*
         * primaryexp -> prefixexp { `.' NAME | `[' exp `]' | `:' NAME funcargs |
         * funcargs }
         */
        FuncState fs = this.fs;
        this.prefixexp(v);
        for (; ; ) {
            switch (this.t.token) {
                case '.': { /* field */
                    this.field(v);
                    break;
                }
                case '[': { /* `[' exp1 `]' */
                    expdesc key = new expdesc();
                    fs.exp2anyreg(v);
                    this.yindex(key);
                    fs.indexed(v, key);
                    break;
                }
                case ':': { /* `:' NAME funcargs */
                    expdesc key = new expdesc();
                    this.next();
                    this.checkname(key);
                    fs.self(v, key);
                    this.funcargs(v);
                    break;
                }
                case '(':
                case TK_STRING:
                case '{': { /* funcargs */
                    fs.exp2nextreg(v);
                    this.funcargs(v);
                    break;
                }
                default:
                    return;
            }
        }
    }


    void simpleexp(expdesc v) {
        /*
         * simpleexp -> NUMBER | STRING | NIL | true | false | ... | constructor |
         * FUNCTION body | primaryexp
         */
        switch (this.t.token) {
            case TK_NUMBER: {
                v.init(VKNUM, 0);
                v.u.setNval(this.t.seminfo.r);
                break;
            }
            case TK_STRING: {
                this.codestring(v, this.t.seminfo.ts);
                break;
            }
            case TK_NIL: {
                v.init(VNIL, 0);
                break;
            }
            case TK_TRUE: {
                v.init(VTRUE, 0);
                break;
            }
            case TK_FALSE: {
                v.init(VFALSE, 0);
                break;
            }
            case TK_DOTS: { /* vararg */
                FuncState fs = this.fs;
                this.check_condition(fs.f.is_vararg != 0, "cannot use " + LUA_QL("...")
                        + " outside a vararg function");
                fs.f.is_vararg &= ~LuaC.VARARG_NEEDSARG; /* don't need 'arg' */
                v.init(VVARARG, fs.codeABC(Lua.OP_VARARG, 0, 1, 0));
                break;
            }
            case '{': { /* constructor */
                this.constructor(v);
                return;
            }
            case TK_FUNCTION: {
                this.next();
                this.body(v, false, this.linenumber);
                return;
            }
            default: {
                this.primaryexp(v);
                return;
            }
        }
        this.next();
    }


    int getunopr(int op) {
        switch (op) {
            case TK_NOT:
                return OPR_NOT;
            case '-':
                return OPR_MINUS;
            case '#':
                return OPR_LEN;
            default:
                return OPR_NOUNOPR;
        }
    }


    // 根据token取得运算符编号
    int getbinopr(int op) {
        switch (op) {
            case '+':
                return OPR_ADD;
            case '-':
                return OPR_SUB;
            case '*':
                return OPR_MUL;
            case '/':
                return OPR_DIV;
            case '%':
                return OPR_MOD;
            case '^':
                return OPR_POW;
            case TK_CONCAT:
                return OPR_CONCAT;
            case TK_NE:
                return OPR_NE;
            case TK_EQ:
                return OPR_EQ;
            case '<':
                return OPR_LT;
            case TK_LE:
                return OPR_LE;
            case '>':
                return OPR_GT;
            case TK_GE:
                return OPR_GE;
            case TK_AND:
                return OPR_AND;
            case TK_OR:
                return OPR_OR;
            default:
                return OPR_NOBINOPR;
        }
    }

    static class Priority {
        final byte left; /* left priority for each binary operator */

        final byte right; /* right priority */

        public Priority(int i, int j) {
            left = (byte) i;
            right = (byte) j;
        }
    }

    ;

    static Priority[] priority = {  /* ORDER OPR */
            new Priority(6, 6), new Priority(6, 6), new Priority(7, 7), new Priority(7, 7), new Priority(7, 7),  /* `+' `-' '*' `/' `%' */
            new Priority(10, 9), new Priority(5, 4),                 /* power and concat (right associative) */
            new Priority(3, 3), new Priority(3, 3),                  /* equality and inequality */
            new Priority(3, 3), new Priority(3, 3), new Priority(3, 3), new Priority(3, 3),  /* order */
            new Priority(2, 2), new Priority(1, 1)                   /* logical (and/or) */
    };

    static final int UNARY_PRIORITY = 8;  /* priority for unary operators */


    /*
     ** subexpr -> (simpleexp | unop subexpr) { binop subexpr }
     ** where `binop' is any binary operator with a priority higher than `limit'
     */
    int subexpr(expdesc v, int limit) {
        int op;
        int uop;
        this.enterlevel();
        uop = getunopr(this.t.token);
        if (uop != OPR_NOUNOPR) {
            this.next();
            this.subexpr(v, UNARY_PRIORITY);
            fs.prefix(uop, v);
        } else
            this.simpleexp(v);
        /* expand while operators have priorities higher than `limit' */
        op = getbinopr(this.t.token);
        while (op != OPR_NOBINOPR && priority[op].left > limit) {
            expdesc v2 = new expdesc();
            int nextop;
            this.next();
            fs.infix(op, v);
            /* read sub-expression with higher priority */
            nextop = this.subexpr(v2, priority[op].right);
            fs.posfix(op, v, v2);
            op = nextop;
        }
        this.leavelevel();
        return op; /* return first untreated operator */
    }

    void expr(expdesc v) {
        this.subexpr(v, 0);
    }

    /* }==================================================================== */



    /*
     ** {======================================================================
     ** Rules for Statements
     ** =======================================================================
     */


    boolean block_follow(int token) {
        switch (token) {
            case TK_ELSE:
            case TK_ELSEIF:
            case TK_END:
            case TK_UNTIL:
            case TK_EOS:
                return true;
            default:
                return false;
        }
    }


    void block() {
        /* block -> chunk */
        FuncState fs = this.fs;
        FuncState.BlockCnt bl = new FuncState.BlockCnt();
        fs.enterblock(bl, false);
        this.chunk();
        LuaC._assert(bl.breaklist.i == NO_JUMP);
        fs.leaveblock();
    }


    /*
     ** structure to chain all variables in the left-hand side of an
     ** assignment
     */
    static class LHS_assign {
        LHS_assign prev;
        /* variable (global, local, upvalue, or indexed) */
        expdesc v = new expdesc();
    }

    ;


    /*
     ** check whether, in an assignment to a local variable, the local variable
     ** is needed in a previous assignment (to a table). If so, save original
     ** local value in a safe place and use this safe copy in the previous
     ** assignment.
     */
    void check_conflict(LHS_assign lh, expdesc v) {
        FuncState fs = this.fs;
        int extra = fs.freereg;  /* eventual position to save local variable */
        boolean conflict = false;
        for (; lh != null; lh = lh.prev) {
            if (lh.v.k == VINDEXED) {
                if (lh.v.u.s.info == v.u.s.info) {  /* conflict? */
                    conflict = true;
                    lh.v.u.s.info = extra;  /* previous assignment will use safe copy */
                }
                if (lh.v.u.s.aux == v.u.s.info) {  /* conflict? */
                    conflict = true;
                    lh.v.u.s.aux = extra;  /* previous assignment will use safe copy */
                }
            }
        }
        if (conflict) {
            fs.codeABC(Lua.OP_MOVE, fs.freereg, v.u.s.info, 0); /* make copy */
            fs.reserveregs(1);
        }
    }


    void assignment(LHS_assign lh, int nvars) {
        expdesc e = new expdesc();
        this.check_condition(VLOCAL <= lh.v.k && lh.v.k <= VINDEXED,
                "syntax error");
        if (this.testnext(',')) {  /* assignment -> `,' primaryexp assignment */
            LHS_assign nv = new LHS_assign();
            nv.prev = lh;
            this.primaryexp(nv.v);
            if (nv.v.k == VLOCAL)
                this.check_conflict(lh, nv.v);
            this.assignment(nv, nvars + 1);
        } else {  /* assignment . `=' explist1 */
            int nexps;
            this.checknext('=');
            nexps = this.explist1(e);
            if (nexps != nvars) {
                this.adjust_assign(nvars, nexps, e);
                if (nexps > nvars)
                    this.fs.freereg -= nexps - nvars;  /* remove extra values */
            } else {
                fs.setoneret(e);  /* close last expression */
                fs.storevar(lh.v, e);
                return;  /* avoid default */
            }
        }
        e.init(VNONRELOC, this.fs.freereg - 1);  /* default assignment */
        fs.storevar(lh.v, e);
    }


    int cond() {
        /* cond -> exp */
        expdesc v = new expdesc();
        /* read condition */
        this.expr(v);
        /* `falses' are all equal here */
        if (v.k == VNIL)
            v.k = VFALSE;
        fs.goiftrue(v);
        return v.f.i;
    }


    void breakstat() {
        FuncState fs = this.fs;
        FuncState.BlockCnt bl = fs.bl;
        boolean upval = false;
        while (bl != null && !bl.isbreakable) {
            upval |= bl.upval;
            bl = bl.previous;
        }
        if (bl == null)
            this.syntaxerror("no loop to break");
        if (upval)
            fs.codeABC(Lua.OP_CLOSE, bl.nactvar, 0, 0);
        fs.concat(bl.breaklist, fs.jump());
    }


    void whilestat(int line) {
        /* whilestat -> WHILE cond DO block END */
        FuncState fs = this.fs;
        int whileinit;
        int condexit;
        FuncState.BlockCnt bl = new FuncState.BlockCnt();
        this.next();  /* skip WHILE */
        whileinit = fs.getlabel();
        condexit = this.cond();
        fs.enterblock(bl, true);
        this.checknext(TK_DO);
        this.block();
        fs.patchlist(fs.jump(), whileinit);
        this.check_match(TK_END, TK_WHILE, line);
        fs.leaveblock();
        fs.patchtohere(condexit);  /* false conditions finish the loop */
    }

    void repeatstat(int line) {
        /* repeatstat -> REPEAT block UNTIL cond */
        int condexit;
        FuncState fs = this.fs;
        int repeat_init = fs.getlabel();
        FuncState.BlockCnt bl1 = new FuncState.BlockCnt();
        FuncState.BlockCnt bl2 = new FuncState.BlockCnt();
        fs.enterblock(bl1, true); /* loop block */
        fs.enterblock(bl2, false); /* scope block */
        this.next(); /* skip REPEAT */
        this.chunk();
        this.check_match(TK_UNTIL, TK_REPEAT, line);
        condexit = this.cond(); /* read condition (inside scope block) */
        if (!bl2.upval) { /* no upvalues? */
            fs.leaveblock(); /* finish scope */
            fs.patchlist(condexit, repeat_init); /* close the loop */
        } else { /* complete semantics when there are upvalues */
            this.breakstat(); /* if condition then break */
            fs.patchtohere(condexit); /* else... */
            fs.leaveblock(); /* finish scope... */
            fs.patchlist(fs.jump(), repeat_init); /* and repeat */
        }
        fs.leaveblock(); /* finish loop */
    }


    int exp1() {
        expdesc e = new expdesc();
        int k;
        this.expr(e);
        k = e.k;
        fs.exp2nextreg(e);
        return k;
    }


    void forbody(int base, int line, int nvars, boolean isnum) {
        /* forbody -> DO block */
        FuncState.BlockCnt bl = new FuncState.BlockCnt();
        FuncState fs = this.fs;
        int prep, endfor;
        this.adjustlocalvars(3); /* control variables */
        this.checknext(TK_DO);
        prep = isnum ? fs.codeAsBx(Lua.OP_FORPREP, base, NO_JUMP) : fs.jump();
        fs.enterblock(bl, false); /* scope for declared variables */
        this.adjustlocalvars(nvars);
        fs.reserveregs(nvars);
        this.block();
        fs.leaveblock(); /* end of scope for declared variables */
        fs.patchtohere(prep);
        endfor = (isnum) ? fs.codeAsBx(Lua.OP_FORLOOP, base, NO_JUMP) : fs
                .codeABC(Lua.OP_TFORLOOP, base, 0, nvars);
        fs.fixline(line); /* pretend that `Lua.OP_FOR' starts the loop */
        fs.patchlist((isnum ? endfor : fs.jump()), prep + 1);
    }


    void fornum(LuaString varname, int line) {
        /* fornum -> NAME = exp1,exp1[,exp1] forbody */
        FuncState fs = this.fs;
        int base = fs.freereg;
        this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_INDEX, 0);
        this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_LIMIT, 1);
        this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_STEP, 2);
        this.new_localvar(varname, 3);
        this.checknext('=');
        this.exp1(); /* initial value */
        this.checknext(',');
        this.exp1(); /* limit */
        if (this.testnext(','))
            this.exp1(); /* optional step */
        else { /* default step = 1 */
            fs.codeABx(Lua.OP_LOADK, fs.freereg, fs.numberK(LuaLong.valueOf(1)));
            fs.reserveregs(1);
        }
        this.forbody(base, line, 1, true);
    }


    void forlist(LuaString indexname) {
        /* forlist -> NAME {,NAME} IN explist1 forbody */
        FuncState fs = this.fs;
        expdesc e = new expdesc();
        int nvars = 0;
        int line;
        int base = fs.freereg;
        /* create control variables */
        this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_GENERATOR, nvars++);
        this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_STATE, nvars++);
        this.new_localvarliteral(RESERVED_LOCAL_VAR_FOR_CONTROL, nvars++);
        /* create declared variables */
        this.new_localvar(indexname, nvars++);
        while (this.testnext(','))
            this.new_localvar(this.str_checkname(), nvars++);
        this.checknext(TK_IN);
        line = this.linenumber;
        this.adjust_assign(3, this.explist1(e), e);
        fs.checkstack(3); /* extra space to call generator */
        this.forbody(base, line, nvars - 3, false);
    }


    void forstat(int line) {
        /* forstat -> FOR (fornum | forlist) END */
        FuncState fs = this.fs;
        LuaString varname;
        FuncState.BlockCnt bl = new FuncState.BlockCnt();
        fs.enterblock(bl, true); /* scope for loop and control variables */
        this.next(); /* skip `for' */
        varname = this.str_checkname(); /* first variable name */
        switch (this.t.token) {
            case '=':
                this.fornum(varname, line);
                break;
            case ',':
            case TK_IN:
                this.forlist(varname);
                break;
            default:
                this.syntaxerror(LUA_QL("=") + " or " + LUA_QL("in") + " expected");
        }
        this.check_match(TK_END, TK_FOR, line);
        fs.leaveblock(); /* loop scope (`break' jumps to this point) */
    }


    int test_then_block() {
        /* test_then_block -> [IF | ELSEIF] cond THEN block */
        int condexit;
        this.next(); /* skip IF or ELSEIF */
        condexit = this.cond();
        this.checknext(TK_THEN);
        this.block(); /* `then' part */
        return condexit;
    }


    void ifstat(int line) {
        /* ifstat -> IF cond THEN block {ELSEIF cond THEN block} [ELSE block]
         * END */
        FuncState fs = this.fs;
        int flist;
        IntPtr escapelist = new IntPtr(NO_JUMP);
        flist = test_then_block(); /* IF cond THEN block */
        while (this.t.token == TK_ELSEIF) {
            fs.concat(escapelist, fs.jump());
            fs.patchtohere(flist);
            flist = test_then_block(); /* ELSEIF cond THEN block */
        }
        if (this.t.token == TK_ELSE) {
            fs.concat(escapelist, fs.jump());
            fs.patchtohere(flist);
            this.next(); /* skip ELSE (after patch, for correct line info) */
            this.block(); /* `else' part */
        } else
            fs.concat(escapelist, flist);
        fs.patchtohere(escapelist.i);
        this.check_match(TK_END, TK_IF, line);
    }

    void localfunc() {
        expdesc v = new expdesc();
        expdesc b = new expdesc();
        FuncState fs = this.fs;
        this.new_localvar(this.str_checkname(), 0);
        v.init(VLOCAL, fs.freereg);
        fs.reserveregs(1);
        this.adjustlocalvars(1);
        this.body(b, false, this.linenumber);
        fs.storevar(v, b);
        /* debug information will only see the variable after this point! */
        fs.getlocvar(fs.nactvar - 1).startpc = fs.pc;
    }


    void localstat() {
        /* stat -> LOCAL NAME {`,' NAME} [`=' explist1] */
        int nvars = 0;
        int nexps;
        expdesc e = new expdesc();
        do {
            this.new_localvar(this.str_checkname(), nvars++);
        } while (this.testnext(','));
        if (this.testnext('='))
            nexps = this.explist1(e);
        else {
            e.k = VVOID;
            nexps = 0;
        }
        this.adjust_assign(nvars, nexps, e);
        this.adjustlocalvars(nvars);
    }


    boolean funcname(expdesc v) {
        /* funcname -> NAME {field} [`:' NAME] */
        boolean needself = false;
        this.singlevar(v);
        while (this.t.token == '.')
            this.field(v);
        if (this.t.token == ':') {
            needself = true;
            this.field(v);
        }
        return needself;
    }


    void funcstat(int line) {
        /* funcstat -> FUNCTION funcname body */
        boolean needself;
        expdesc v = new expdesc();
        expdesc b = new expdesc();
        this.next(); /* skip FUNCTION */
        needself = this.funcname(v);
        this.body(b, needself, line);
        fs.storevar(v, b);
        fs.fixline(line); /* definition `happens' in the first line */
    }


    void exprstat() {
        /* stat -> func | assignment */
        FuncState fs = this.fs;
        LHS_assign v = new LHS_assign();
        this.primaryexp(v.v);
        if (v.v.k == VCALL) /* stat -> func */
            LuaC.SETARG_C(fs.getcodePtr(v.v), 1); /* call statement uses no results */
        else { /* stat -> assignment */
            v.prev = null;
            this.assignment(v, 1);
        }
    }

    void retstat() {
        /* stat -> RETURN explist */
        FuncState fs = this.fs;
        expdesc e = new expdesc();
        int first, nret; /* registers with returned values */
        this.next(); /* skip RETURN */
        if (block_follow(this.t.token) || this.t.token == ';')
            first = nret = 0; /* return no values */
        else {
            nret = this.explist1(e); /* optional return values */
            if (hasmultret(e.k)) {
                fs.setmultret(e);
                if (e.k == VCALL && nret == 1) { /* tail call? */
                    LuaC.SET_OPCODE(fs.getcodePtr(e), Lua.OP_TAILCALL);
                    LuaC._assert(Lua.GETARG_A(fs.getcode(e)) == fs.nactvar);
                }
                first = fs.nactvar;
                nret = Lua.LUA_MULTRET; /* return all values */
            } else {
                if (nret == 1) /* only one single value? */
                    first = fs.exp2anyreg(e);
                else {
                    fs.exp2nextreg(e); /* values must go to the `stack' */
                    first = fs.nactvar; /* return all `active' values */
                    LuaC._assert(nret == fs.freereg - first);
                }
            }
        }
        fs.ret(first, nret);
    }


    boolean statement() {
        int line = this.linenumber; /* may be needed for error messages */
        switch (this.t.token) {
            case TK_IF: { /* stat -> ifstat */
                this.ifstat(line);
                return false;
            }
            case TK_WHILE: { /* stat -> whilestat */
                this.whilestat(line);
                return false;
            }
            case TK_DO: { /* stat -> DO block END */
                this.next(); /* skip DO */
                this.block();
                this.check_match(TK_END, TK_DO, line);
                return false;
            }
            case TK_FOR: { /* stat -> forstat */
                this.forstat(line);
                return false;
            }
            case TK_REPEAT: { /* stat -> repeatstat */
                this.repeatstat(line);
                return false;
            }
            case TK_FUNCTION: {
                this.funcstat(line); /* stat -> funcstat */
                return false;
            }
            case TK_LOCAL: { /* stat -> localstat */
                this.next(); /* skip LOCAL */
                if (this.testnext(TK_FUNCTION)) /* local function? */
                    this.localfunc();
                else
                    this.localstat();
                return false;
            }
            case TK_RETURN: { /* stat -> retstat */
                this.retstat();
                return true; /* must be last statement */
            }
            case TK_BREAK: { /* stat -> breakstat */
                this.next(); /* skip BREAK */
                this.breakstat();
                return true; /* must be last statement */
            }
            default: {
                this.exprstat();
                return false; /* to avoid warnings */
            }
        }
    }

    void chunk() {
        /* chunk -> { stat [`;'] } */
        boolean islast = false;
        this.enterlevel();
        while (!islast && !block_follow(this.t.token)) {
            islast = this.statement();
            this.testnext(';');
            LuaC._assert(this.fs.f.maxstacksize >= this.fs.freereg
                    && this.fs.freereg >= this.fs.nactvar);
            this.fs.freereg = this.fs.nactvar; /* free registers */
        }
        this.leavelevel();
    }

    /* }====================================================================== */

}
